package com.romeo_foxtrot.labs.webproxy;

import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProxyServlet extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = -8372180765848034072L;

	private static final Logger logger = LoggerFactory
			.getLogger(ProxyServlet.class);

	// private static final Pattern COOKIE_ITEM = Pattern
	// .compile("(?i)(.*?)=(.*?)(?:;\\s*|$)");

	/*
	 * 
	 */
	// private static final Pattern COOKIE_PATH_PATTERN = Pattern
	// .compile("(?i)(?<=(^|;\\s*)path\\s*=).*?(?=$|;)");
	private static final Pattern COOKIE_PATH_VALUE = Pattern
			.compile("(?i)(?<=(^|;\\s?)path\\s?=).*?(?=$|;)");

	private static final Pattern COOKIE_DOMAIN = Pattern
			.compile("(?i)(?:^|;\\s*)domain\\s*=.*?(?=$|;)");

	// private static final Pattern REQUEST_COOKIE_HEADER = Pattern
	// .compile("(?i)cookie");

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {

		String targetUrl = targetUrl(req);
		logger.trace(targetUrl);
		if (targetUrl == null) {
			// getServletContext().getRequestDispatcher("/index.html").forward(
			// req, resp);
			return;
			// targetUrl = "about:blank";
			/*
			 * TODO customer error
			 */
		}

		String cookiePath = req.getContextPath() + req.getServletPath() + "/"
				+ targetDomain(targetUrl);

		HttpClient client = getHttpClient(req);

		GetMethod method = new GetMethod(req.getScheme() + "://" + targetUrl);
		method.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);

		/*
		 * TODO set headers
		 */
		@SuppressWarnings("rawtypes")
		Enumeration headerNames = req.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			logger.trace("iiiiiiii");

			String headerName = (String) headerNames.nextElement();
			logger.trace(headerName);

			@SuppressWarnings("rawtypes")
			Enumeration headerValues = req.getHeaders(headerName);
			while (headerValues.hasMoreElements()) {
				String headerValue = (String) headerValues.nextElement();
				logger.trace(headerValue);

				// Matcher matcher = REQUEST_COOKIE_HEADER.matcher(headerName);
				// if (matcher.matches()) {
				// matcher = COOKIE_PATH_VALUE.matcher(headerValue);
				// if (!matcher.find() ||
				// !cookiePath.equals(matcher.group())) {
				// logger.trace("skipped cookie: " + headerValue);
				// continue;
				// }
				// logger.trace("uploading cookie: " + headerValue);
				// }
				method.addRequestHeader(headerName, headerValue);
			}
			logger.trace("iiiiiiii");
		}

		try {
			int statusCode = client.executeMethod(method);

			/*
			 * status code
			 */
			resp.setStatus(statusCode);

			/*
			 * headers (including cookie)
			 */
			for (Header h : method.getResponseHeaders()) {

				String name = h.getName();
				String value = h.getValue();

				logger.trace("oooooooo");
				logger.trace(h.toString());
				// for (HeaderElement e : h.getElements()) {
				// logger.trace(e);
				// }
				logger.trace("name: " + name);
				logger.trace("value: " + value);
				logger.trace("oooooooo");

				if ("Set-Cookie".equals(name)) {
					// continue;
					/*
					 * cookie is now managed by http client
					 * 
					 * modify cookie domain and path
					 * 
					 * using regex
					 */
					// StringBuilder sb = new StringBuilder();
					// String[] items = value.split(";\\s*");
					// if (items.length > 0) {
					//
					// /*
					// * first one
					// */
					// String item = items[0];
					// item = modifyCookieItem(path, item);
					// if (item != null) {
					// sb.append(item);
					// }
					//
					// for (int i = 1; i < items.length; i++) {
					// item = items[i];
					// item = modifyCookieItem(path, item);
					// if (item != null) {
					// sb.append("; " + item);
					// }
					// }
					//
					// value = sb.toString();
					// }
					Matcher matcher = COOKIE_PATH_VALUE.matcher(value);
					value = matcher.replaceFirst(cookiePath);

					matcher = COOKIE_DOMAIN.matcher(value);
					value = matcher.replaceFirst("");

				} else if ("Content-Length".equals(name)) {
					/*
					 * content length may change
					 */
					continue;
				}

				resp.addHeader(name, value);
			}

			/*
			 * content
			 * 
			 * FIXME update static text links
			 */
			IOUtils.copy(method.getResponseBodyAsStream(),
					resp.getOutputStream());
			logger.trace("...");

			/*
			 * XXX nullable?
			 */
			Header contentTypeHeader = method.getResponseHeader("Content-Type");
			String contentType = contentTypeHeader.getValue();

			Header contentLengthHeader = method
					.getResponseHeader("Content-Length");
			int contentLength = Integer
					.parseInt(contentLengthHeader.getValue());

			if (matchContentType(contentType, "text/html")) {

				// /*
				// * TODO insert js just before </body>
				// */
				// String proxyScript = "<script type='text/javascript' src='"
				// + req.getContextPath() + "/proxy.js'></script>";

				// PrintWriter writer = resp.getWriter();
				// BufferedReader reader = new BufferedReader(
				// new InputStreamReader(method.getResponseBodyAsStream()));
				// String line = reader.readLine();
				// while (line != null) {
				//
				// int p = line.indexOf("</body>");
				// if (p != -1) {
				// /*
				// * insert js
				// */
				// logger.trace("********");
				// logger.trace(line);
				// logger.trace("********");
				// line = line.substring(0, p) + proxyScript
				// + line.substring(p);
				//
				// contentLength += proxyScript.length();
				// }
				// logger.trace(line);
				//
				// writer.println(line);
				//
				// line = reader.readLine();
				// }

				// String responseBody = method.getResponseBodyAsString();
				// int p = responseBody.indexOf("</body>");
				// if (p != -1) {
				// /*
				// * insert js
				// */
				// responseBody = responseBody.substring(0, p) + proxyScript
				// + responseBody.substring(p);
				//
				// } else {
				// responseBody += proxyScript;
				// }
				// logger.trace(responseBody);
				// resp.getWriter().print(responseBody);
				//
				// contentLength += proxyScript.length();
				// resp.setContentLength(contentLength);
				IOUtils.copy(method.getResponseBodyAsStream(),
						resp.getOutputStream());

			} else if (matchContentType(contentType, "text/css")) {
				/*
				 * TODO fix urls starting with /
				 * 
				 * TODO be aware of Accept-Encoding request header and
				 * Content-Encoding response header
				 */
				IOUtils.copy(method.getResponseBodyAsStream(),
						resp.getOutputStream());
			} else if (matchContentType(contentType, "text/javascript")) {
				/*
				 * TODO wrap dom root components, e.g. window, document,
				 * location, ...
				 */
				IOUtils.copy(method.getResponseBodyAsStream(),
						resp.getOutputStream());
			}

			/*
			 * TODO persist cookie for users across their sessions
			 * 
			 * method.getResponseHeader("set-cookie")
			 */

		} finally {
			method.releaseConnection();
		}

		return;
	}

	// private String modifyCookieItem(String path, String item) {
	//
	// if (item.startsWith("path=")) {
	// item = "path=" + path;
	// } else if (item.startsWith("domain=")) {
	// /*
	// * skip
	// */
	// item = null;
	// }
	//
	// return item;
	// }

	private String targetDomain(String targetUrl) {

		String targetDomain;

		int firstSlash = targetUrl.indexOf("/");
		if (firstSlash == -1) {
			targetDomain = targetUrl;
		} else {
			targetDomain = targetUrl.substring(0, firstSlash);
		}

		return targetDomain;
	}

	private boolean matchContentType(String contentType, String expected) {
		return contentType.equals(expected)
				|| contentType.startsWith(expected + ";");
	}

	private HttpClient getHttpClient(HttpServletRequest req) {

		HttpClient client;

		HttpSession session = req.getSession();
		String attributeName = HttpClient.class.getName();
		client = (HttpClient) session.getAttribute(attributeName);
		if (client == null) {
			// MultiThreadedHttpConnectionManager manager = new
			// MultiThreadedHttpConnectionManager();
			//
			// HttpConnectionManagerParams params = new
			// HttpConnectionManagerParams();
			// params.setDefaultMaxConnectionsPerHost(10);
			// params.setMaxTotalConnections(100);
			//
			// manager.setParams(params);
			//
			// client = new HttpClient(manager);
			client = new HttpClient();
			session.setAttribute(attributeName, client);
		}

		return client;
	}

	private String targetUrl(HttpServletRequest req) {

		// String targetUrl = req.getParameter("t");
		String targetUrl;

		logger.trace("========");
		logger.trace("requestURL: " + req.getRequestURL());
		logger.trace("contextPath: " + req.getContextPath());
		logger.trace("servletPath: " + req.getServletPath());
		logger.trace("--------");
		logger.trace("scheme: " + req.getScheme());
		logger.trace("serverName: " + req.getServerName());
		logger.trace("serverPort: " + req.getServerPort());
		logger.trace("requestURI: " + req.getRequestURI());
		logger.trace("queryString: " + req.getQueryString());
		logger.trace("========");

		/*
		 * gets the part after context and servlet paths
		 */
		StringBuffer requestURL = req.getRequestURL();
		String contextPath = req.getContextPath();
		String servletPath = req.getServletPath();
		int start = requestURL.indexOf(contextPath) + contextPath.length()
				+ servletPath.length() + 1;

		if (start < requestURL.length()) {
			targetUrl = requestURL.substring(start);
		} else {
			targetUrl = null;
		}

		return targetUrl;
	}

}
